#include <cstdio>
#include "luacv.hpp"
using namespace cv;

static LUACV_RETURN_CODE str2pic(const char* str, Mat &result_pic)
{
    int x, y, base = 0, cursor = 0, ptr = 0;
    char x_str[4] = {}, y_str[4] = {};

    for (int i=0; str[base] != ':'; i++)
    {
        x_str[i] = str[base++];
    }
    base++;
    for (int i=0; str[base] != ':'; i++)
    {
        y_str[i] = str[base++];
    }
    base++;
    x = atoi(x_str);
    y = atoi(y_str);

    result_pic.create(x, y, CV_8UC1);
    // imwrite("inter.png", result_pic);

    for (cursor = 0; str[cursor+base] !='@' && str[cursor+base] != '\0'; cursor++)
    {
        char c = str[cursor+base];
        switch (c)
        {
#define WRITE_BIN_PIXEL(p1,p2,p3,p4) \
do{ \
    result_pic.at<uchar>(ptr / y, ptr % y) = (p1);\
    result_pic.at<uchar>((ptr+1) / y, (ptr+1) % y) = (p2); \
    result_pic.at<uchar>((ptr+2) / y, (ptr+2) % y) = (p3); \
    result_pic.at<uchar>((ptr+3) / y, (ptr+3) % y) = (p4); \
}while(0)
            case 'f':
                WRITE_BIN_PIXEL(0,0,0,0);
            break;
            case 'e':
                WRITE_BIN_PIXEL(0,0,0,255);
            break;
            case 'd':
                WRITE_BIN_PIXEL(0,0,255,0);
            break;
            case 'c':
                WRITE_BIN_PIXEL(0,0,255,255);
            break;
            case 'b':
                WRITE_BIN_PIXEL(0,255,0,0);
            break;
            case 'a':
                WRITE_BIN_PIXEL(0,255,0,255);
            break;
            case '9':
                WRITE_BIN_PIXEL(0,255,255,0);
            break;
            case '8':
                WRITE_BIN_PIXEL(0,255,255,255);
            break;
            case '7':
                WRITE_BIN_PIXEL(255,0,0,0);
            break;
            case '6':
                WRITE_BIN_PIXEL(255,0,0,255);
            break;
            case '5':
                WRITE_BIN_PIXEL(255,0,255,0);
            break;
            case '4':
                WRITE_BIN_PIXEL(255,0,255,255);
            break;
            case '3':
                WRITE_BIN_PIXEL(255,255,0,0);
            break;
            case '2':
                WRITE_BIN_PIXEL(255,255,0,255);
            break;
            case '1':
                WRITE_BIN_PIXEL(255,255,255,0);
            break;
            case '0':
                WRITE_BIN_PIXEL(255,255,255,255);
            break;
            default:
                return LUACV_ARITHM_ERR;
#undef WRITE_BIN_PIXEL
        }
        ptr += 4;
    }
    // printf("finish with %c:%d @: %d\n", str[base+cursor],base+cursor,str[base+cursor] );
    if (str[cursor+base] == 0) return LUACV_SUCCESS;

    base += cursor+1;
    for (cursor = 0; str[cursor+base] != '\0'; cursor++)
    {
        char c = str[cursor+base];
        // printf("got %c: %d@base : %d\n", c, c, cursor+base);
        switch (c)
        {
            case '0':
                result_pic.at<uchar>(ptr / y, ptr % y) = 255;
                break;
            case '1':
                result_pic.at<uchar>(ptr / y, ptr % y) = 0;
                break;
            default:
                return LUACV_ARITHM_ERR;
        }
        ptr += 1;
    }

    return LUACV_SUCCESS;
}

static LUACV_RETURN_CODE templateMatching(const Mat &srcImage, const Mat &templateImage, double *diff, Point *matchLoc = nullptr)
{
    int result_cols, result_rows;
    double minVal, maxVal;
    Mat result;
    Point minLoc, maxLoc;

    if (srcImage.data == nullptr || templateImage.data == nullptr)
    {
        return LUACV_PARAM_ERR;
    }

    result_cols = srcImage.cols - templateImage.cols + 1;
    result_rows = srcImage.rows - templateImage.rows + 1;
    if (result_cols < 0 || result_rows < 0)
    {
        return LUACV_ARITHM_ERR;
    }

    result.create(result_cols, result_rows, CV_32FC1);
    // enum { TM_SQDIFF=0, TM_SQDIFF_NORMED=1, TM_CCORR=2, TM_CCORR_NORMED=3, TM_CCOEFF=4, TM_CCOEFF_NORMED=5 };
    matchTemplate(srcImage, templateImage, result, TM_SQDIFF_NORMED); //最好匹配为1,值越小匹配越差
    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

    if (matchLoc != nullptr)
    {
        *matchLoc = minLoc;
    }
    // 把minval送给lua就行了
    *diff = minVal;
    return LUACV_SUCCESS;
}

static LUACV_RETURN_CODE templateMatchingWithStr(const Mat &srcImage, const char *tmpBm, double *diff, Point *matchLoc = nullptr)
{
    Mat tmpImage, src_bin;
    if (str2pic(tmpBm, tmpImage) != LUACV_SUCCESS || srcImage.rows == 0 || srcImage.cols == 0)
    {
        return LUACV_ARITHM_ERR;
    }
    threshold(srcImage, src_bin, 140, 255, THRESH_BINARY_INV);
    return templateMatching(src_bin, tmpImage, diff, matchLoc);
}

static int lua_facede_cvMatchTemplate(lua_State *L)
{
    Mat src, tmp, src_gray;
    Point matchLoc;
    double retval;
    const char *names[2];
    int n = lua_gettop(L);

    if (n < 2)
    {
        lua_pushstring(L, "必须参数数量不足");
        lua_error(L);
    }

    for (int i = 0; i < 2; i++)
    {
        if (!lua_isstring(L, -1))
        {
            lua_pushstring(L, "参数类型不正确：期望字符串类型");
            lua_error(L);
        }
        names[i] = lua_tostring(L, -i - 1);
    }
    // printf("names: %s, %s\n", names[0], names[1]);

    src = imread(names[1]);
    tmp = imread(names[0]);

    auto ret_state = templateMatching(src, tmp, &retval, &matchLoc);

    if (ret_state == LUACV_PARAM_ERR)
    {
        lua_pushstring(L, "输入参数存在问题");
        lua_error(L);
    }

    if (ret_state == LUACV_ARITHM_ERR)
    {
        lua_pushstring(L, "发生错误");
        lua_error(L);
    }

    // 变量获取规则满足FIFO， 先压入的变量作为第一个
    lua_pushnumber(L, retval);
    lua_pushnumber(L, matchLoc.x);
    lua_pushnumber(L, matchLoc.y);
    return 3;
}

static int lua_facede_cvMatchDict(lua_State *L)
{
    Mat src, tmp;
    Point matchLoc;

    double retval;
    const char *str[2];
    int n = lua_gettop(L);

    if (n < 2)
    {
        lua_pushstring(L, "必须参数数量不足");
        lua_error(L);
    }

    for (int i = 0; i < 2; i++)
    {
        if (!lua_isstring(L, -1))
        {
            lua_pushstring(L, "参数类型不正确：期望字符串类型");
            lua_error(L);
        }
        str[i] = lua_tostring(L, - i - 1);
    }
    // printf("names: %s, %s\n", names[0], names[1]);

    src = imread(str[1],IMREAD_GRAYSCALE);
    auto ret_state = templateMatchingWithStr(src, str[0], &retval, &matchLoc);

    if (ret_state == LUACV_PARAM_ERR)
    {
        lua_pushstring(L, "输入参数存在问题");
    }
    
    if (ret_state == LUACV_ARITHM_ERR)
    {
        lua_pushstring(L, "发生错误");
        lua_error(L);
    }

    // 变量获取规则满足FIFO， 先压入的变量作为第一个
    lua_pushnumber(L, retval);
    lua_pushnumber(L, matchLoc.x);
    lua_pushnumber(L, matchLoc.y);
    return 3;
}

static const luaL_Reg luaLibs[] = {
    {"cvMatchTemplate", lua_facede_cvMatchTemplate},
    {"cvMatchDict", lua_facede_cvMatchDict},
    {NULL, NULL}
};

extern "C" int luaopen_libluacv(lua_State *L)
{
    luaL_newlib(L, luaLibs);
    return 1;
}

// int main(int argc, char* argv[])
// {
//     assert(argc == 3);
//     Mat src = imread(argv[1], IMREAD_GRAYSCALE);
//     Mat tmp = imread(argv[2], IMREAD_GRAYSCALE);
//     double res;
//     templateMatching(src, tmp, &res);
//     printf("min diff: %f\n", res);
// }